package Farsail::Manager;

use strict; 
use warnings;

use Carp;
use lib '/home/yewenb/svn/personal/yewenbin/Term-PromptEdit/lib';
use Data::Dumper qw(Dumper);
use Farsail;
use YAML;
use Path::Class;
use Return::Value;
use XML::Simple;
my ($farsail, $config);

sub init {
    my $pkg = shift;
    $farsail = shift;
    $config = $farsail->config;
}

sub ACTION_print {
    my $self = shift;
    my $args = $farsail->args;
    require Template;
    my $tt = Template->new({
        INCLUDE_PATH => $config->rootdir,
    })  or die $Template::ERROR;
    my $content;
    $tt->process( $Farsail::Config::FARSAIL_CONFIG.'.tpl', {
        config => $config,
    }, \$content);
    print $content;
    if ( $args->output ) {
        my $fh = file($args->output)->openw;
        print {$fh} $content;
    }
}

sub ACTION_config {
    my $self = shift;
    my $args = $farsail->args;
    if ( $args->pbs ) {
        $self->config_pbs();
    }
    elsif ( $args->cluster ) {
        $self->config_cluster();
    }
    my $save_dir;
    if ( $args->dir ) {
        $save_dir = $args->dir;
    } elsif ( $args->inplace ) {
        $save_dir = $config->rootdir;
    } else {
        $save_dir = '.';
    }
    my $conffile = file($save_dir, $Farsail::Config::FARSAIL_CONFIG);
    $farsail->dispatch('print', output => $conffile );
    if ( $config->get('manager.save_database') ) {
        my $dbfile = file($save_dir, file($config->databases_config_file)->basename);
        YAML::DumpFile($dbfile, $config->database);
    }
}

sub config_pbs {
    my $self = shift;
    require IPC::Run3;
    IPC::Run3->import('run3');
    
    my ($rv, $out, $err);

    # config compute nodes
    my ($pbsnodes, $nodes);
    print "Get PBS compute nodes list...\n";
    run3("pbsnodes -x", \*STDIN, \$out, \$err);
    if ( $? != 0 ) {
        print "Run pbsnodes failed, the error message are:\n";
        print $err, "\n";
        print "Please install pbs client again!\n";
        exit(-1);
    }
    $pbsnodes = XMLin($out, ForceArray=>1);
    $nodes = [sort map {$_->{name}[0]} @{$pbsnodes->{Node}}];
    print "Compute Nodes are list following:\n";
    print join("\n", @$nodes), "\n";
    if ( $farsail->y_n("edit the list?", 'n') ) {
        require Term::PromptEdit;
        my $editor = Term::PromptEdit->new;
        my $input = $editor->prompt(join("\n", @$nodes));
        $nodes = read_config(\$input);
    }
    $config->remove('^pbs\.nodes\.');
    for ( 0..$#$nodes ) {
        $config->set("pbs.nodes.$_", $nodes->[$_]);
    }

    # config default queue
    my ($pbs_queue, $lines);
    print "Get PBS queue name...\n";
    $rv = run3("qmgr -c 'print server default_queue'", \*STDIN, \$out, \$err);
    if ( !$rv ) {
        print "Run qmgr failed, the error message are:\n";
        print $err, "\n";
        print "Please install pbs client again!\n";
        exit(-1);
    }
    $lines = read_config(\$out);
    if ( @$lines && $lines->[0] =~ /default_queue\s*=\s*(\w+)/ ) {
        $pbs_queue = $1;
    }
    $config->set('pbs.queue', $farsail->prompt("PBS queue is:", $pbs_queue));
}

sub config_cluster {
    require Term::Prompt;
    require Term::PromptEdit;
    Term::Prompt->import('prompt');
    my $self = shift;
    my $clusters = $config->cluster;
    my @menu = (
        ['add cluster' => sub { $self->add_cluster }] ,
        (map {
            my $c = $_;
            ["edit cluster $c" => sub { $self->edit_cluster($c) }],
            } sort keys %$clusters),
        (map {
            my $c = $_;
            ["delete cluster $c" => sub {
                 print "remove cluster $c\n";
                 $config->remove("^cluster.".quotemeta($c."."));
             }],
            } sort keys %$clusters),
        );
    my $choice = prompt('m', {
        title => "Configure pl/proxy cluster:",
        items => [ map { $_->[0] } @menu ],
        cols => 1,
    }, '', '');
    $menu[$choice][1]->();
}

sub edit_cluster {
    die "Sorry not inplement yet!\n";
}

sub add_cluster {
    my $self = shift;
    my $clusters = $config->cluster;
    my $cluster_name;
 GET_CLUSTER_NAME: 
    {
        $cluster_name = prompt('e', "name of cluster:", "", "", "[a-z0-9_]+");
        if ( exists $clusters->{$cluster_name} ) {
            print "$cluster_name is in use! Known clusters are:\n";
            print join("\n", sort keys %$clusters), "\n";
            redo GET_CLUSTER_NAME;
        }
    }
    my $is_create = prompt('y', "Create new database nodes list?", 'y/n', 'y');
    my $nodes_num;
 GET_NODES_NUM: 
    {
        $nodes_num = prompt("n", "Number of database nodes", "power of 2", 16);
        if ( $nodes_num < 0 || !power_of_two($nodes_num) ) {
            redo GET_NODES_NUM;
        }
    }
    my ($user, $nodes_host, $nodes_name_prefix);
    if ( $is_create ) {
        $nodes_name_prefix = prompt('e', "Prefix for database name:", "", "", "[a-z0-9_]+");
        $nodes_host = $config->pbs->{nodes};
    GET_HOST: 
        {
            if ( $nodes_host ) {
                print "Known host are:\n";
                print join("\n", @$nodes_host), "\n";
            }
            print "Add host: (enter to quit) ";
            my $input = <STDIN>;
            $input =~ s/^\s+//; $input =~ s/\s+$//;
            if ( $input ) {
                push @$nodes_host, $input;
                redo GET_HOST;
            }
        }
        $user = prompt('e', 'Database user:', '', '', '[a-z0-9_]+');
    }
    my $help_msg;
    my $db = $config->database;
    my $known_db = join("\n", map { "# " . join("\t", $_, @{$db->{$_}}{qw/dbname host user/}) } grep { $db->{$_}{dbd} eq 'Pg'} sort keys %$db);
    if ( $known_db ) {
        $help_msg .= <<EOT;
## Known database are list following ####
# db_id dbname host
$known_db

EOT
    }
    $help_msg .= <<EOT;
## Set proxy database here ##
# If use exists database only db_id need, otherwise
# db_id dbname host user password port

## Set nodes database here ##
# if use exists database only db_id need, otherwise
# node_index db_id dbname user host port
EOT
    my @nodes_list = map { [$_, # index
                            $nodes_name_prefix.$_, # db_id
                            $nodes_name_prefix.$_, # dbname
                            '~', # host
                            $user, # user
                            '~', # password
                            '~', # port
                            ] } 1..$nodes_num; 
    if ( $is_create ) {
        my ($n, $m) = ( $nodes_num, scalar(@$nodes_host) );
        my $j = $m - 1 - $n % $m;
        my $k = 0;
        for my $i ( 0..$j ) {
            for ( 0..int($n/$m)-1 ) {
                $nodes_list[$k++][3] = $nodes_host->[$i] || '~';
            }
        }
        for my $i ( $j+1 .. $m-1 ) {
            for ( 0..int($n/$m) ) {
                $nodes_list[$k++][3] = $nodes_host->[$i] || '~';
            }
        }
    }
    $help_msg .= join("\n", map {join("\t", @{$_})} @nodes_list)."\n";
    my $editor = Term::PromptEdit->new();
    my $cluster_config = $editor->prompt(
        $help_msg, sub { parse_cluster_config(\$_[0], $nodes_num) } );
    my $new = parse_cluster_config( \$cluster_config, $nodes_num);
    $config->set("cluster.$cluster_name.proxy", $new->{proxy}{db_id});
    $new->{proxy}{dbd} = 'Pg';
    $db->{delete $new->{proxy}{db_id}} = $new->{proxy};
    for ( 0..$nodes_num-1 ) {
        my $db_id = delete $new->{nodes}[$_]{db_id};
        $config->set("cluster.$cluster_name.nodes.$_", $db_id);
        $new->{nodes}[$_]{dbd} = 'Pg';
        $db->{$db_id} = $new->{nodes}[$_];
    }
    $config->set('manager.save_database', 1);
}

sub parse_cluster_config {
    my ($conf, $num) = @_;
    my @fields = qw(db_id dbname host user password port);
    my $ln = 0;
    my @lines = split /\n/, $$conf;
    my $readline = sub {
        while ( $ln < @lines ) {
            $lines[$ln] =~ s/^\s+//;
            $lines[$ln] =~ s/\s*$//;
            $lines[$ln] =~ s/#.*$//;
            $ln++;
            return $lines[$ln-1] if $lines[$ln-1];
        }
        return;
    };
    my $parser = sub {
        my $line = shift;
        my @r = split /\s+/, $line;
        my $dbconf = {};
        for ( 0..$#r ) {
            last if $_ > $#fields;
            if ( $r[$_] eq '~' ) {
                next;
            }
            $dbconf->{$fields[$_]} = $r[$_];
        }
        return $dbconf;
    };
    my $cluster;
    my $line;
    if ( $line = $readline->() ) {
        $cluster->{proxy} = $parser->($line);
        if ( !$cluster->{proxy} || $cluster->{proxy}{db_id} =~ /^\d+$/ ) {
            return failure "Proxy db configure error at line $ln, ";
        }
    } else {
        return failure "No config found! ";
    }
    @fields = qw(index db_id dbname host user port);
    while ( $line = $readline->() ) {
        my $db = $parser->($line) || return failure "Node db configure error at line $ln, ";
        my $index = delete $db->{index};
        $cluster->{nodes}[$index-1] = $db;
    }
    if ( @{$cluster->{nodes}} != $num ) {
        return failure "The number of node db is not $num, ";
    }
    return $cluster;
}

sub read_config {
    my $content = shift;
    my @lines = grep { $_ } map {
        s/^\s+//;
        s/\s*$//;
        s/#.*$//;
        $_;
    } grep { !/^\s*#/ } split /\n/, $$content;
    return \@lines;
}

sub power_of_two {
    return ($_[0]&($_[0]-1)) == 0;
}

sub ACTION_setup {
    my $self = shift;
    my $args = $farsail->args;
    my $all_clusters = $config->cluster;
    my $cluster_name = $args->cluster || (keys %$all_clusters)[0];
    my $cluster = $all_clusters->{$cluster_name};
    if ( $args->createdb ) {
        my $createdb = $config->get('programs.createdb')||'createdb';
        # TODO: warning if proxydb not create
        for my $db_id ( $cluster->{proxy}, @{$cluster->{nodes}} ) {
            my $db = $config->database($db_id);
            if ( !$db->exists ) {
                my %opt = $db->psql_options;
                my $dbname = delete $opt{'-d'};
                print join(" ", $createdb, %opt, $dbname), "\n";
                $farsail->system($createdb, %opt, $dbname);
            }
        }
        return;
    }
    my %default = (
        host => 'localhost',
        port => '5432',
        user => getlogin(),
    );
    my $default_cluster = $config->default_cluster;
    my $sql = <<SQL;
SELECT plproxy.set_default_cluster_name('$default_cluster');
DELETE FROM plproxy.conf WHERE cluster_name='$cluster_name';
INSERT INTO plproxy.conf (cluster_name, host, port, dbname, username) VALUES
SQL
    my @values;
    for my $dbid ( @{$cluster->{nodes}} ) {
        my $db = $config->database($dbid);
        push @values, [$cluster_name,
                       map {
                           exists $db->{$_} ? $db->{$_} : $default{$_}
                       } qw/host port dbname user/];

    }
    $sql .= join(",\n", map { '('. join(',', map {"'$_'"} @{$_}).')'} @values);
    $sql .= ";\n";
    print $sql, "\n";
    $farsail->cluster($cluster_name)->do($sql) || die DBI->errstr;
}

sub run_sql {
    my ($db, $script) = @_;
    if ( ref $script eq 'SCALAR' ) { # $script is string
        print "DO sql in $db\n";
        print $$script;
        $farsail->db($db)->do($$script) or die DBI->errstr;
    }
    else {
        my $psql = $config->get('programs.psql')||'psql';
        print join(" ", $psql,
                   $config->database($db)->psql_options,
                   "-f", $script), "\n";
        $farsail->system($psql,
                         $config->database($db)->psql_options,
                         "-f", $script);
    }
}

sub ACTION_install {
    my $self = shift;
    my $args = $farsail->args;
    my ($sql) = @{$args->other_args};
    if ( !$sql || !-e $sql ) {
        die ($sql ? "Can't find sql file '$sql'!\n"
                 : "No sql file given!\n");
    }
    my $all_clusters = $config->cluster;
    my $cluster_name = $args->cluster || (keys %$all_clusters)[0];
    my ($common_sql, $handled, $dbname);
    open(my $fh, "<", $sql) or die "Can't open file $sql: $!";
    while ( <$fh> ) {
        if ( /^\\c\s+(cluster\.\S+)/) {
            $dbname = $1;
            $handled = 1;
            last;
        }
        $common_sql .= $_
    }
    if ( $args->proxy ) {
        if ( !$handled ) {
            run_sql($all_clusters->{$cluster_name}{proxy}, $sql);
        } elsif ( $common_sql ) {
            run_sql($all_clusters->{$cluster_name}{proxy}, \$common_sql);
        }
    } else {
        if ( !$handled ) {
            for ( @{$all_clusters->{$cluster_name}{nodes}} ) {
                run_sql($_, $sql);
            }
        } elsif ( $common_sql ) {
            for ( @{$all_clusters->{$cluster_name}{nodes}} ) {
                run_sql($_, \$common_sql);
            }
        }
    }
    if ( $handled ) {
        while ( $dbname ) {
            my $db = $config->get($dbname);
            if ( !$db ) {
                die "Unknown database '$dbname'\n";
            }
            $dbname = undef;
            my $sql;
            while ( <$fh> ) {
                if ( /^\\c\s+(cluster\.\S+)/) {
                    $dbname = $1;
                    last;
                }
                $sql .= $_;
            }
            run_sql($db, \$sql);
        }
    }
}

1;
